﻿using System;
using System.Collections.Generic;
using System.Text;
using ShootSearch.Core;
using System.Diagnostics;
using ShootSearch.Util;
using System.IO;
using System.Collections;
using System.Xml.Serialization;
using log4net;

namespace ShootSearch.FileSystem
{
    public class STFileSystem: IFileSystem
    {
        /// <summary>
        /// 常量:文件名stfile.main
        /// </summary>
        public const string FILENAME = "stfile.main";
        /// <summary>
        /// 常量:索引文件名stfile.index
        /// </summary>
        public const string INDEXNAME = "stfile.index";

        public const string INDEXLINE = "{0},{1},{2}";

        public const string DEF_FSID = "default";

        protected FileSystemConfig config;
        protected string fsid = "default";
        protected object locker = new object();
        protected string ioPath = string.Empty;
        protected string contextPath = string.Empty;
        protected Dictionary<string, IndexOffset> indexDict;

        protected FileStream fileStream;
        
        protected StreamWriter indexWriter;


        public STFileSystem()
        {
            config = new FileSystemConfig();
            IsClosed = true;
            indexDict = new Dictionary<string, IndexOffset>();
        }

        #region IConfigable Members

        public ShootSearch.Core.SpiderManConfig Config
        {
            get
            {
                return config;
            }
            set
            {
                if (value is FileSystemConfig)
                {
                    config = value as FileSystemConfig;
                }
            }
        }

        public ILog Logger { get; set; }

        #endregion

        #region IFileSystem Members

        public bool IsClosed { get; protected set; }

        public string FSID
        {
            get { return fsid; }
            set
            {
                Close();
                fsid = value;
                if (!string.IsNullOrEmpty(ioPath))
                {
                    CreateOpen();

                    if (FileSystemIDChanged != null)
                    {
                        FileSystemIDChanged(this, new FileSystemEventArgs(fsid));
                    }
                }


            }
        }

        public long Length
        {
            get
            {
                if (fileStream != null) return fileStream.Length;
                else return 0;
            }
        }

        public event FileSystemActionEventHandler FileSystemRead;

        public event FileSystemActionEventHandler FileSystemWrite;

        public event FileSystemActionEventHandler FileSystemIDChanged;

        public bool CreateOpen(params object[] args)
        {
            //首先初始化
            indexDict.Clear();

            if (string.IsNullOrEmpty(ioPath) && (args == null || args.Length == 0))
            {
                return false;
            }
            else
            {
                Close();
                if(args.Length==0)
                {
                     ioPath = IO.FormatDirName(ioPath);
                }
                else 
                {
                    ioPath = IO.FormatDirName(args[0].ToString());
                    if (args.Length > 1)
                    {
                        fsid = args[1].ToString();
                    }
                    
                }

                if (fsid == DEF_FSID)
                {
                    List<string> fsidList = this.ListAllFSIDs(ioPath);
                    if(fsidList.Count>0)fsid = fsidList[0];
                }

                
                contextPath = IO.FormatDirName(ioPath + fsid);

                lock (locker)
                {
                    //确定文件读写方式
                    //创建或着读取文件
                    fileStream = new FileStream(contextPath + FILENAME, FileMode.OpenOrCreate, FileAccess.ReadWrite);
                    //binWriter = new BinaryWriter(fileStream);

                    //None WriteOnlye mode needs to load the index
                    if (config.WriteOnly)
                    {
                        InitializeIndexWriter(true);
                    }
                    else
                    {
                        LoadIndex();
                    }
                }
                IsClosed = false;
            }
            return true;
        }

        public IFileObject Read(string id)
        {
            if (config.WriteOnly) return null;

            id = id.Trim();
            if (indexDict.ContainsKey(id))
            {
                IFileObject fileEntity = new FileObject();

                lock (locker)
                { 
                    long pos = 0;
                    if(indexDict[id].NewOffset==0)
                    {
                        pos = indexDict[id].OriginalOffset;
                    }
                    else pos = indexDict[id].NewOffset;
                    fileStream.Seek(pos, SeekOrigin.Begin);
                    fileEntity.Read(fileStream);

                    if (this.FileSystemRead != null) FileSystemRead(this, new FileSystemEventArgs(this.FSID));

                }
                return fileEntity;
            }
            else return null;
        }

        public string ReadText(string id)
        {
            IFileObject fileEntity = Read(id);
            if (fileEntity == null)
            {
                return null;
            }
            else
            {                
                return fileEntity.Encoding.GetString(fileEntity.Content);
            }
        }

        public bool Write(IFileObject file)
        {
            file.Url = file.Url.Trim();

            lock (locker)
            {
                if (!config.WriteOnly)
                {
                    if (indexDict.ContainsKey(file.Url))
                    {
                        VirtualDelete(file.Url);
                        //Update new offset
                        byte[] buffLenght = BitConverter.GetBytes(fileStream.Length);
                        fileStream.Write(buffLenght, 0, buffLenght.Length);
                        //binWriter.Write(fileStream.Length);
                        indexDict[file.Url].NewOffset = fileStream.Length;
                    }
                    else
                    {
                        IndexOffset offset = new IndexOffset(fileStream.Length, 0);
                        indexDict.Add(file.Url, offset);
                    }
                }
                else
                {
                    indexWriter.WriteLine(string.Format(INDEXLINE, fileStream.Length, 0, file.Url));
                }

                fileStream.Seek(0, SeekOrigin.End);
                //file.Write(fileStream);
                byte[] buff = file.ToBytes();
                fileStream.Write(buff, 0, buff.Length);

                if (this.FileSystemWrite != null) FileSystemWrite(this, new FileSystemEventArgs(this.FSID));

            }

            return true;
        }      

        public bool WriteText(string id, string text)
        {
            return Write(new FileObject(id,text));
        }

        public bool Delete(string id)
        {
            if (!config.WriteOnly)
            {
                id = id.Trim();
                if (indexDict.ContainsKey(id))
                {
                    VirtualDelete(id);
                    indexDict.Remove(id);
                    return true;
                }
            }
            return false;
        }

        public List<string> ListAllFSIDs(params object[] args)
        {
           

            List<string> fsidList = new List<string>();

            string listpath = ioPath;

            if (args != null && args.Length > 0) listpath = args[0].ToString();
            
            if (!string.IsNullOrEmpty(listpath) && Directory.Exists(listpath))
            {
                string[] fsids = Directory.GetDirectories(listpath);
                foreach (string item in fsids)
                {
                    fsidList.Add(new DirectoryInfo(item).Name); 
                }


            }

            return fsidList;
        }

        public List<string> ListFileNames()
        {
            List<string> urls = new List<string>();
            if (!config.WriteOnly)
            {
                urls.AddRange(indexDict.Keys);
            }
            return urls;
        }

        public void Close()
        {
            if (!IsClosed)
            {
                lock (locker)
                {
                    Flush();

                    fileStream.Close();
                    fileStream.Dispose();

                    if (config.WriteOnly)
                    {
                        indexWriter.Close();
                        indexWriter.Dispose();
                    }

                    IsClosed = true; 
                }

            }
        }

        public bool Exist(string id)
        {
            return !config.WriteOnly && indexDict.ContainsKey(id.Trim());
        }

        public void Flush()
        {
            fileStream.Flush();
            if (!config.WriteOnly)
            {
                //Create new output
                InitializeIndexWriter(false);

                Dictionary<string, IndexOffset>.Enumerator ide = indexDict.GetEnumerator();
                while (ide.MoveNext())
                {
                    indexWriter.WriteLine(string.Format(INDEXLINE,
                        ide.Current.Value.OriginalOffset, ide.Current.Value.NewOffset, ide.Current.Key));
                }
                indexWriter.Close();
                indexWriter.Dispose();
            }
       
        }

        #endregion

        #region Protected Methods
        protected void InitializeIndexWriter(bool append)
        {
            indexWriter = new StreamWriter(contextPath + INDEXNAME, append, config.Encoding);
            indexWriter.AutoFlush = true;
        }

        protected void LoadIndex()
        {
            if (!File.Exists(contextPath + INDEXNAME)) return;
            StreamReader sr = new StreamReader(contextPath + INDEXNAME, config.Encoding);
            string line = sr.ReadLine();
            while (line != null)
            {
                string[] att = line.Split(',');
                if (att != null && att.Length >= 3)
                {
                    long pos1 = 0;
                    long pos2 = 0;
                    if (long.TryParse(att[0].Trim(), out pos1)
                        && long.TryParse(att[1].Trim(), out pos2))
                    {
                        string url = line.Substring(att[0].Length + att[1].Length + 2);
                        if (!indexDict.ContainsKey(url.Trim()))
                        {
                            indexDict.Add(url.Trim(), new IndexOffset(pos1, pos2));
                        }
                    }
                }

                line = sr.ReadLine();
            }
            sr.Close();
        }

        protected void VirtualDelete(string url)
        {
            IndexOffset offset = indexDict[url];
            long pos = offset.OriginalOffset;
            if (offset.NewOffset != 0)
            {
                pos = offset.NewOffset;
            }

            //标记原始文件为删除状态
            //修改其DateTime为最新的偏移值
            //+4表示跳过header长度信息[int]
            fileStream.Seek(pos + 4, SeekOrigin.Begin);
            byte[] b = BitConverter.GetBytes(true);
            fileStream.Write(b, 0, b.Length);
            //binWriter.Write(true);
        } 
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Close();
        }

        #endregion
    }

    #region IndexOffset
    public class IndexOffset
    {
        public long OriginalOffset { get; set; }
        public long NewOffset { get; set; }

        public IndexOffset() { }

        public IndexOffset(long originalOffset, long newOffset)
        {
            this.OriginalOffset = originalOffset;
            this.NewOffset = newOffset;
        }
    } 
    #endregion

    #region FileSystemConfig
    public class FileSystemConfig : SpiderManConfig
    {
        protected string encodingName = Encoding.UTF8.HeaderName;
        [XmlIgnore]
        public Encoding Encoding { get; protected set; }
        public string EncodingName
        {
            get { return encodingName; }
            set
            {
                try
                {
                    Encoding encode = Encoding.GetEncoding(value);
                    encodingName = encode.HeaderName;
                    Encoding = encode;
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex);
                }

            }
        }

        public bool WriteOnly { get; set; }

        public FileSystemConfig()
        {
            Encoding = Encoding.UTF8;
        }
    } 
    #endregion
}
